<?php
/*
 * Yet Another php Template Parse class
 *
 * By: Nicolas Grecco
 * For: YAPP Framework
 *
 */

class yappTPL
{

    private $currentTemplates      = array();
    private $currentTemplate       = false;
    private $defaultDirectory      = false;
    private $cacheDirectory        = false;
    private $currentCacheDirectory = false;
    private $avoidCache            = false;
    private $replaceString         = '<!---SUBTEMPLATEHERE!--->';
    private $echoReplaceBegin      = "<?php @print \$data['";
    private $echoReplaceEnd        = "'];?>";
    private $echoArrReplaceBegin   = "<?php @print \$data[";
    private $echoArrReplaceEnd     = "];?>";
    private $loop                  = 0;
    private $parsedTimes           = array();

    public function __construct($cacheDirectory, $defaultDirectory, $avoidCache = false)
    {
        $this->cacheDirectory   = $cacheDirectory;
        $this->defaultDirectory = $defaultDirectory;
        $this->avoidCache       = $avoidCache;
    }

    // Set template, uses default directory if not specified
    public function setTemplate($template, $directory = false)
    {
        if (empty($directory)) $directory = $this->defaultDirectory;
        $tplHash = $this->templateCacheHash($template, $directory);
        if (!isset($this->currentTemplates[$tplHash]))
        {
            // Check template cache directory
            $this->checkCacheTemplateDirectory(basename($directory), $template);
            // Load template in parts
            $this->loadTemplate($template, $directory, $tplHash);
        }
        $this->currentTemplate = $tplHash;
    }

    // Create tempate cache directory if it does not exists
    public function checkCacheTemplateDirectory($directory, $template)
    {
        // Check module directory
        $parentDir = $this->cacheDirectory.$directory;
        if (!is_dir($parentDir))
        {
            if (!mkdir($parentDir))
            {
                die("UNABLE TO CREATE TEMPLATE CACHE PARENT DIR");
            }
        }
        // Check template directory
        $cacheDir  = $parentDir.'/'.$template;
        if (!is_dir($cacheDir))
        {
            if (!mkdir($cacheDir))
            {
                die("UNABLE TO CREATE TEMPLATE CACHE DIR");
            }
        }
        // Set current cache directory
        $this->currentCacheDirectory = $cacheDir.'/';
    }

    // Make template hash
    private function templateCacheHash($template, $directory)
    {
        $tplHash = md5($directory.'/'.$template.'.html');
        return $tplHash;
    }

    // Load template parts
    private function loadTemplate($template, $directory, $tplHash)
    {

        if (!$this->cachedTemplate($tplHash))
        {
            $templateFile = $directory.'/'.$template.'.html';
            if (file_exists($templateFile))
            {
                $file = file_get_contents($templateFile);
                $this->splitTemplates($file, $tplHash);
            }
            else
            {
                die($templateFile. ' - Template does not exists');
            }
        }
        else
        {

        }
    }

    // Check if we are going to use cached template
    private function cachedTemplate($tplHash)
    {
        return $this->avoidCache;
    }

    // Split templates into subtemplates
    private function splitTemplates($template, $hash)
    {
        $templateParts = array();
        $template = $this->replaceEchos($template);
        $this->splitUntilSmallestTemplate($template, $templateParts);
        $this->spliTemplatesInPartsAccordingToSubtemplates($templateParts, $hash);
    }

    // Get smallests template content and assign to parts
    private function splitUntilSmallestTemplate(&$templateHtml, &$templateParts)
    {
        if ($this->loop == 100) die("Unexpected error (Probably infinite loop)!! Please check sintax!!");
        // Loop avoider
        $this->loop++;
        // Strings
        $templateBeginDeclaration = '<template name="';
        $templateBeginClose       = '">';
        $templateCloseDeclaration = '</template>';
        $beginLength              = strlen($templateBeginDeclaration);
        $closeLength              = strlen($templateCloseDeclaration);
        // Check if has declaration
        $templateHtml = trim($templateHtml);
        if (($templateHtml != '') && ($templateHtml != $this->replaceString))
        {
            $lastBeginPos = strrpos($templateHtml, $templateBeginDeclaration);
            if ($lastBeginPos !== false)
            {
                // Get template name
                $closePosition = strpos($templateHtml, $templateBeginClose, $lastBeginPos + $beginLength);
                $templateName  = substr($templateHtml, $lastBeginPos+$beginLength, ($closePosition-($lastBeginPos+$beginLength)));
                $templateName  = $this->sanitizeTemplateName($templateName);
                // Get closing position
                $closeTemplatePosition = strpos($templateHtml, $templateCloseDeclaration, $closePosition);
                if ($closeTemplatePosition !== false)
                {
                    // Get template content
                    $templateStart   = $closePosition+strlen($templateBeginClose);
                    $templateContent = trim(substr($templateHtml, $templateStart, $closeTemplatePosition-$templateStart));
                    // Assign content into parts
                    $templateParts[$templateName] = $templateContent;
                    // Remove template content from original html
                    $allTemplate = substr($templateHtml, $lastBeginPos, $closeTemplatePosition-$lastBeginPos+$closeLength);
                    $templateHtml = str_replace($allTemplate, $this->replaceString, $templateHtml);
                    // Recursive call until no more templates to replace
                    $this->splitUntilSmallestTemplate($templateHtml, $templateParts);
                }
                else
                {
                    die("Error in template '$templateName'. Missing closing tag.");
                }
            }
            else
            {
                die("Error in template. Please check");
            }
        }
    }

    // Always sanitize template name just in case
    private function sanitizeTemplateName($name)
    {
        return trim(htmlentities(strip_tags($name)));
    }

    // Split template parsing lines according to subtemplates
    private function spliTemplatesInPartsAccordingToSubtemplates($templateParts, $hash)
    {
        if (!empty($templateParts))
        {
            $this->parsedTimes[$hash] = array();
            foreach ($templateParts as $templateName => $content)
            {
                $content = explode($this->replaceString, $content);
                if (!empty($content))
                {
                    $this->currentTemplates[$hash][$templateName] = array();
                    $part = 0;
                    foreach ($content as $templatePart)
                    {
                        $templatePart = trim($templatePart);
                        if (!empty($templatePart))
                        {
                            // Set stuff
                            $fileName = $templateName.'.part'.$part.'.html';
                            $this->currentTemplates[$hash][$templateName][]= $this->currentCacheDirectory.$fileName;
                            $this->parsedTimes[$hash][$templateName] = 0;
                            $part++;
                            // Save template in cache
                            $fp = fopen($this->currentCacheDirectory.$fileName, 'w');
                            fwrite($fp,$templatePart);
                        }
                    }
                }
            }
        }
        else
        {
            die("EMPTY TEMPLATE!");
        }
    }

    // Prepare echos for replacement
    private function replaceEchos($templateHtml)
    {
        $templateHtml = str_replace('{{[', $this->echoArrReplaceBegin, $templateHtml);
        $templateHtml = str_replace(']}}', $this->echoArrReplaceEnd, $templateHtml);
        $templateHtml = str_replace('{{', $this->echoReplaceBegin, $templateHtml);
        $templateHtml = str_replace('}}', $this->echoReplaceEnd, $templateHtml);
        return $templateHtml;
    }

    // Parse tempate with vars!
    public function parse($template, $data, $templateFile = false, $templateDirectory = false)
    {
        // Check if it overrides setted
        if ($templateFile)
        {
            $lastTemplate = $this->currentTemplate;
            $this->loadTemplate($templateFile, $templateDirectory);
        }
        // If not current template setted, die with error
        if (empty($this->currentTemplate))
        {
            die("No template to parse!");
        }
        // Parse template
        if (!empty($this->currentTemplates[$this->currentTemplate][$template]))
        {
            $parseIndex = $this->getParseIndex($this->currentTemplate, $template);
            $includeTpl = $this->currentTemplates[$this->currentTemplate][$template][$parseIndex];
            include($includeTpl);
            echo "\n";
        }
        else
        {
            die("Undefined template $template");
        }
        // Revert to last in case it was modified
        if (!empty($lastTemplate))
        {
            $this->currentTemplate = $lastTemplate;
        }
    }

    // Get parse index according to parse times called
    private function getParseIndex($hash, $template)
    {
        $availableParses = count($this->currentTemplates[$hash][$template]);
        if($this->parsedTimes[$hash][$template] >= $availableParses)
        {
            $this->parsedTimes[$hash][$template] = 0;
        }
        return $this->parsedTimes[$hash][$template]++;
    }
}
?>